/**
 * \file 3d.h
 * 3D Graphics Primitives & Mesh.
 *
 * Provides the following:
 *	- 3D transformations and orientations;
 *	- Representation of triangles and meshes;
 *	- Classes for creating meshes of basic
 *	  geometric objects (primitives).
 *	- helper class for creating VBO arrays
 **
 * Authors: Dan Horgan, Sean Micklethwaite, Mac Duy Hai
 * License... etc.
 **/

#ifndef _3D_H_
#define _3D_H_

#include <vector>

#include "vector.h"
#include "aux-math.h"

#include "wtftypes.h"
#include "graphics.h"
#include "graphicsdata.h"
#include "texture.h"
#include "glsl.h"


#define NORMAL_OFFSET		3
#define TEXTURE_OFFSET		6
#define TANGENT_OFFSET(tex) (6 + 2 * tex)
#define FEATURE_TANGENTS 	01

#define TexCoordSet std::vector<fVector2D>



/**
 * Triangle in 3D space given by three connected points
 * in clockwise order (?)
 */
struct Triangle
{
	tVector		vertices[3];

	Triangle (){};
	Triangle (tVector v1, tVector v2, tVector v3){
		vertices[0] = v1;
		vertices[1] = v2;
		vertices[2] = v3;
	};

	virtual ~Triangle() {};

	const tVector *getVertices() const{
		return vertices;
	}

	tVector& operator[](int i){
		return vertices[i];
	};
	tVector operator[](int i) const{
		return vertices[i];
	};
};


//// Orientation

struct Orientation3D;
struct Orientation3DOps : public MatrixOps<tScalar, 3, 3> {
	typedef Orientation3D tMatrix;

	// Specialize to keep Orientation3D type when dimension is still 3x3
	template<unsigned _M, unsigned _N> struct Redim {
		typedef typename MatrixType<tScalar, _M, _N>::tType result;
	};
	template<> struct Redim<3, 3> {
		typedef Orientation3D result;
	};
};

template <>
struct MatrixTraits<Orientation3D> : public MatrixTraits<Matrix<tScalar, 3, 3, Orientation3DOps> > {
};

struct Orientation3D : public Matrix<tScalar, 3, 3, Orientation3DOps>
{
	Orientation3D()
		: Matrix<tScalar, 3, 3, Orientation3DOps>(
			Matrix<tScalar, 3, 3, Orientation3DOps>::identity()){

	};
	Orientation3D(tScalar zero)
		: Matrix<tScalar, 3, 3, Orientation3DOps>(){
	};

	/// Implicit conversion from other matrix types
	template <class T, class Ops>
	Orientation3D(const Matrix<T,3,3,Ops>& m): Matrix<tScalar, 3, 3, Orientation3DOps>(m) {};

	Orientation3D(tVector x, tVector y, tVector z) {
		(*this)[0] = x; (*this)[1] = y; (*this)[2] = z;
	};

	Orientation3D	getDerivative(const tVector& w);

	template <class tElem>
	inline void getOpenGLMatrix(tElem *m) const {
		m[0] = (*this)[0][0];
		m[1] = (*this)[1][0];
		m[2] = (*this)[2][0];
		m[3] = 0;
		m[4] = (*this)[0][1];
		m[5] = (*this)[1][1];
		m[6] = (*this)[2][1];
		m[7] = 0;
		m[8] = (*this)[0][2];
		m[9] = (*this)[1][2];
		m[10] = (*this)[2][2];
		m[11] = 0;

		// Translation
		m[12] = 0;
		m[13] = 0;
		m[14] = 0;
		m[15] = 1;
	};

	template <class tElem>
	inline static Orientation3D FromOpenGLMatrix(const tElem *m) {
		Orientation3D ori;
		ori[0][0] = m[0];
		ori[1][0] = m[1];
		ori[2][0] = m[2];
		ori[0][1] = m[4];
		ori[1][1] = m[5];
		ori[2][1] = m[6];
		ori[0][2] = m[8];
		ori[1][2] = m[9];
		ori[2][2] = m[10];
		return ori;
	};

	static Orientation3D Rotation(const tDirection& vAxis, Angle a);
};



//// Transformation

struct Transformation3D;
struct Transformation3DOps : public MatrixOps<tScalar, 4, 4> {
	typedef Transformation3D tMatrix;

	// Specialize to keep Transformation3D type when dimension is still 4x4
	template<unsigned _M, unsigned _N> struct Redim {
		typedef typename MatrixType<tScalar, _M, _N>::tType result;
	};
	template<> struct Redim<4, 4> {
		typedef Transformation3D result;
	};
};

template <>
struct MatrixTraits<Transformation3D> : public MatrixTraits<Matrix<tScalar, 4, 4, Transformation3DOps> > {
};

struct Transformation3D : public Matrix<tScalar, 4, 4, Transformation3DOps>
{
	typedef Matrix<tScalar, 4, 4, Transformation3DOps> tMatrixType;

	inline Transformation3D(){};

	inline Transformation3D(const tVector& vTransl, const Orientation3D& o) {
		for(int i=0;i<3;i++) (*this)[i][3] = vTransl[i];
		for(int i=0;i<3;i++) for(int j=0;j<3;j++) (*this)[i][j] = o[i][j];
		(*this)[3][3] = 1.0;
	};

	/** Composition of transformations **/
	inline Transformation3D then(const Transformation3D& t) const{
		return (tMatrixType)t * *this;
	};

	inline tVector operator * (const tVector v) const {
		VectorType<tScalar, 4>::tType vTmp(v[0], v[1], v[2], 1.0);
		vTmp = (tMatrixType)(*this) * (vTmp);
		return tVector(vTmp[0], vTmp[1], vTmp[2]);
	};

	static Transformation3D scale(tScalar s) {
		Transformation3D t(identity());
		for(int i=0;i<3;i++) t[i][i] = s;
		return t;
	};

	static Transformation3D scale(const tVector& v) {
		Transformation3D t(identity());
		for(int i=0;i<3;i++) t[i][i] = v[i];
		return t;
	};

	inline tVector getTranslation() const {
		tVector v;
		for(int i=0;i<3;i++) v[i] = (*this)[i][3];
		return v;
	};
	
	inline Orientation3D getOrientation() const {
		Orientation3D o;
		for(int i=0;i<3;i++) for(int j=0;j<3;j++) o[i][j] = (*this)[i][j];
		return o;
	};

	template <class tElem>
	inline void getOpenGLMatrix(tElem *m) const {
		m[0] = (*this)[0][0];
		m[1] = (*this)[1][0];
		m[2] = (*this)[2][0];
		m[3] = (*this)[3][0];
		m[4] = (*this)[0][1];
		m[5] = (*this)[1][1];
		m[6] = (*this)[2][1];
		m[7] = (*this)[3][1];
		m[8] = (*this)[0][2];
		m[9] = (*this)[1][2];
		m[10] = (*this)[2][2];
		m[11] = (*this)[3][2];

		// Translation
		m[12] = (*this)[0][3];
		m[13] = (*this)[1][3];
		m[14] = (*this)[2][3];
		m[15] = (*this)[3][3];
	};

	template <class tElem>
	inline static Transformation3D FromOpenGLMatrix(const tElem *m) {
		Transformation3D t;
		t[0][0] = m[0];
		t[1][0] = m[1];
		t[2][0] = m[2];
		t[3][0] = m[3];
		t[0][1] = m[4];
		t[1][1] = m[5];
		t[2][1] = m[6];
		t[3][1] = m[7];
		t[0][2] = m[8];
		t[1][2] = m[9];
		t[2][2] = m[10];
		t[3][2] = m[11];
		t[0][3] = m[12];
		t[1][3] = m[13];
		t[2][3] = m[14];
		t[3][3] = m[15];
		return t;
	};
};



namespace WtfGraphics {
	/**
	 * Texture Slots define linking between meshes and texture. Each texture occupies one
	 * texture slot per application of that texture on the mesh.
	 * FIXME: This should be a private class/struct inside the to-be-written Model class
	 */
	struct TextureSlot {
		WtfEngine::Texture::tRef	texture;
		int							blending;
		int							coordSet;
	};

	/**
	 *	Wrapper class for creating Vertex Buffer Object Arrays, enables easier adding of
	 *  vertices, normals and texture coordinates. VBOArrays are used by Meshes to store
	 *  Texture coordinates and vertex normals. The buffer array stores vertex, normals and
	 *  texture coordinates, in a interleaved fashion.
	 *  | Pos.x | Pos.y | Pos.z | Nor.x | Nor.y | Nor.z | [Col.r | Col.g | Col.b |] [ UV1.x | UV1.y | [UV2.x | UV2.y | [..]]] [Tan.x | Tan.y | Tan.z | Tan.w]
	 */
	class VBOArray: public WtfEngine::GcObject {
		private:
			float *buf;				///< array buffer
			int vertices;			///< number of vertices
			int texCoordSets;		///< total number of coordinate sets
			int vertexSize;			///< number of cells occupied by one vertex in the buffer
			int pos;				///< current position in the array
			int tangents;			///< 1 if tangents are used, otherwise 0
			unsigned int ID;		///< VBO ID returned by OpenGL

		public:
			/// Initializes the buffer array to a fixed size.
			VBOArray(int vertices, int texCoordSets, int features = 0) {
				this->vertices	 	= vertices;
				this->texCoordSets 	= texCoordSets;
				this->tangents		= CHECKFLAG(FEATURE_TANGENTS, features) ? 1 : 0;
				this->vertexSize 	= TEXTURE_OFFSET + 2 * texCoordSets + tangents * 4;

				this->buf			= new float[vertices * vertexSize];
				this->ID			= -1;
			};

			/// destroys the buffer
			~VBOArray() {
				delete[] buf;
			};

			/// locates the ith specific vertex
			void seekVertex(int vertex) { pos = vertex * vertexSize;};

			/// locates the ith vertex of the tth triangle
			void seekVertex(int triangle, int vertex) { pos = (triangle * 3 + vertex) * vertexSize; };

			/// writes the vertex information
			void putVertex(float x, float y, float z) {
				buf[pos] = x;
				buf[pos + 1] = y;
				buf[pos + 2] = z;
			};

			/// writes vertex information, casting them to floats
			void putVertex(tVector vect) {
				buf[pos] 		= (float)vect[0];
				buf[pos + 1] 	= (float)vect[1];
				buf[pos + 2] 	= (float)vect[2];
			};

			/// writes normals
			void putNormal(float x, float y, float z) {
				buf[NORMAL_OFFSET + pos] = x;
				buf[NORMAL_OFFSET + pos + 1] = y;
				buf[NORMAL_OFFSET + pos + 2] = z;
			};

			/// writes normals, cast to floats
			void putNormal(tVector vect) {
				buf[NORMAL_OFFSET + pos] 		= (float)vect[0];
				buf[NORMAL_OFFSET + pos + 1] 	= (float)vect[1];
				buf[NORMAL_OFFSET + pos + 2] 	= (float)vect[2];
			};

			/// writes texture coordinate of specified coordinate set
			void putTexCoords(int texSet, float x, float y) {
				buf[TEXTURE_OFFSET + pos + 2 * texSet] = x;
				buf[TEXTURE_OFFSET + pos + 2 * texSet + 1] = y;
			};

			/// Creates the buffer in the graphics memory
			void CreateBuffer();

			/// Binds the buffer in the graphics memory and sets up pointers for rendering
			void Bind();
			/// Unbinds the buffer
			void Release();

			/// Set up a pointer to a coordinate set in the gfx mem. Bind must be called beforehand.
			void BindCoordSet(int i);

			/// Sets up a pointer to the tangents. Bind must be called beforehand
			void BindTangents(int location);

			/// Calculates Tangents for each vertex. Note that the buffer needs to be filled
			/// with other data first and it must contain at least 1 texture coord set.
			void CalculateTangents();

		GC_AUTOSIZE(VBOArray);
	};

	

	/**
	 * Mesh Class.
	 *
	 * A 3D polygonal entity representation composed of a list of triangles,
	 * face normals and optionally vertex normals and multiple texture coordinate sets.
	 */
	class Mesh : public GraphicsData {
	private:
		// A mesh is represented by these. They are also used for Physics
		// Triangles + all other data (vertex normals, texture coordinates)
		// are stored in the Vertex Buffer Object Array vboArray.
		std::vector<Triangle>	 tris;
		std::vector<tVector>	 faceNorms;

		// Vertex Buffer Object related
		VBOArray::tRef 	vboArray;				///< The associated buffer array object
		bool		 	vboCreated;				///< VBO Buffer has been created in the gfx mem
		
	public:
		Mesh(const WtfEngine::StringPool::Entry& name = WtfEngine::StringPool::GetInstance()["Mesh"]) : GraphicsData(name) {
			vboCreated	 = false;
		};
		~Mesh() {};


		const std::vector<Triangle> &getTriangles() const;
		void setTriangles(const std::vector<Triangle>& ts) {
			tris = ts;
		};

		void addTriangle(Triangle t);
		void addMesh(Mesh* m);

		void render();

		int getNumTris() const{
			return tris.size();
		};

		const Triangle& operator[](int i) const{
			return tris[i];
		};

		tVector getFaceNormal(int i) const{
			return faceNorms[i];
		};


		// Vertex Buffer Object related
		void CreateVBO(std::vector<Triangle>*, std::vector<TexCoordSet>*, bool);

		// !! Needs updating
		DATA_RECONSTRUCTIBLE(Mesh, sizeof(Mesh) + (sizeof(Triangle) * tris.size())
				+ (sizeof(tVector) * faceNorms.size()));
	};

	class TransformedMesh: public GraphicsData {
	private:
		GraphicsData::tCacheRef	mrBase;
		tPosition			mvPos;
		Orientation3D		mOri;

	public:
		TransformedMesh(const GraphicsData::tCacheRef& rBase, const tVector& pos, const Orientation3D& ori);

		virtual void render();

		inline Transformation3D getTransform() {
			return Transformation3D(mvPos, mOri);
		};
		inline void setTransform(const Transformation3D& t) {
			mvPos = t.getTranslation();
			mOri = t.getOrientation();
		};

		DATA_LOADABLE(TransformedMesh, GC_ADDEDSIZE_AUTO(GraphicsData, TransformedMesh));
	};

	/**
	 * A model, at the moment, is simply a list of meshes. The meshes
	 * are currently pretransformed, so they appear in the right place
	 * when rendered; I'm unsure whether this is the right behaviour.
	 **/
	class Model : public GraphicsData {
	protected:
		std::vector<GraphicsData::tCacheRef>	mvrMesh;

	public:
		Model(const WtfEngine::StringPool::Entry& sName);

		inline void InsertMesh(const GraphicsData::tCacheRef& rGfx) {
			mvrMesh.push_back(rGfx);
		};

		/// Renders all meshes in the model
		virtual void render();

		DATA_LOADABLE(Model, GC_ADDEDSIZE_AUTO(GraphicsData, Model));
	};
};

#endif
